/*
* Tencent is pleased to support the open source community by making Mars available.
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/

import  MarsServiceProfile  from './MarsServiceProfile'
import MainService from '../../core/MainService'
import PushMessage from '../remote/PushMessage'
import { AppLogic, GlobalContext } from '@ohos/mars'
import { Log, StnLogic, SdtLogic } from '@ohos/mars'
import { MarsTaskProperty } from '../remote/MarsTaskProperty'
import  AbstractTaskWrapper  from '../remote/AbstractTaskWrapper'
import BaseConstants from '../../utils/print/BaseConstants';
import MarsPushMessageFilter from '../remote/MarsPushMessageFilter';
import hilog from '@ohos.hilog';


export class MarsServiceStub implements StnLogic.ICallBack, AppLogic.ICallBack, SdtLogic.ICallBack{
  private static readonly TAG: string = "Mars.Sample.MarsServiceStub";
  private readonly profile: MarsServiceProfile;
  private accountInfo: AppLogic.AccountInfo = new AppLogic.AccountInfo(123, 'abc')
  public static DEVICE_NAME: string = "";
  public static DEVICE_TYPE: string = "ohos-";
  private info: AppLogic.DeviceInfo ;
  private filters: Array<MarsPushMessageFilter>  = [];
  private clientVersion: number = 200;
  public constructor(profile: MarsServiceProfile) {
    this.profile = profile;
    this.info = new AppLogic.DeviceInfo(MarsServiceStub.DEVICE_TYPE, MarsServiceStub.DEVICE_TYPE);
  }
  public onChangFrom(){
    let from = ''
    try {
      from = GlobalContext.getContext().getValue('from_name') as string
    } catch (e) {
      console.info('MarsServiceStub----------err:' + e.message)
    }
    this.accountInfo = new AppLogic.AccountInfo(123, from);
  }

  private static FIXED_HEADER_SKIP: number = 4 + 2 + 2 + 4 + 4;

  private static TASK_ID_TO_WRAPPER: Map<number, AbstractTaskWrapper>  = new Map();

  public send(taskWrapper: AbstractTaskWrapper): number {
    console.info("MarsServiceStub.send(0)")
    let _task: StnLogic.Task = new StnLogic.Task(StnLogic.Task.EShort, 0, "", null);
    console.info("_task:"+JSON.stringify(_task));
    let taskProperties = taskWrapper.getProperties();

    // Set host & cgi path
    let host = taskProperties.get(MarsTaskProperty.OPTIONS_HOST);
    let cgiPath = taskProperties.get(MarsTaskProperty.OPTIONS_CGI_PATH);
    _task.shortLinkHostList = new Array<string>();
    _task.shortLinkHostList.push(host);
    _task.cgi = cgiPath;

    let shortSupport: boolean = taskProperties.get(MarsTaskProperty.OPTIONS_CHANNEL_SHORT_SUPPORT);
    let longSupport: boolean = taskProperties.get(MarsTaskProperty.OPTIONS_CHANNEL_LONG_SUPPORT);
    console.info("shortSupport:"+shortSupport);
    console.info("longSupport:"+longSupport);
    if (shortSupport && longSupport) {
      _task.channelSelect = StnLogic.Task.EBoth;
    } else if (shortSupport) {
      _task.channelSelect = StnLogic.Task.EShort;
    } else if (longSupport) {
      _task.channelSelect = StnLogic.Task.ELong;

    } else {
      console.info("invalid channel strategy");
//      throw new RemoteException("Invalid Channel Strategy");
    }
    console.info("_task.channelSelect:"+_task.channelSelect);

    // Set cmdID if necessary
    let cmdID: number = taskProperties.get(MarsTaskProperty.OPTIONS_CMD_ID);
    if (cmdID != -1) {
      _task.cmdID = cmdID;
    }

    MarsServiceStub.TASK_ID_TO_WRAPPER.set(_task.taskID, taskWrapper);

    // Send
    console.info("now start task with id: " + _task.taskID);
    StnLogic.startTask(_task);
//    if (StnLogic.hasTask(_task.taskID)) {
       console.info("stn task started with id: " + _task.taskID);
//    } else {
//       console.info("stn task start failed with id: " + _task.taskID);
//    }
    return 1;
  }

  public cancel(taskID: number): void {
    console.info("cancel wrapper with taskID=%d using stn stop" + taskID);
    StnLogic.stopTask(taskID);
    MarsServiceStub.TASK_ID_TO_WRAPPER.delete(taskID); // TODO: check return
  }

  private static removeItem<T>(array: Array<T>, item: T) {
    let index = array.indexOf(item);
    if(index>=0 && index < array.length) {
      array.splice(index, 1);
    }
  }

  public registerPushMessageFilter(filter: MarsPushMessageFilter): void {
    MarsServiceStub.removeItem(this.filters, filter);
    this.filters.push(filter);
  }

  public unregisterPushMessageFilter(filter: MarsPushMessageFilter): void  {
    MarsServiceStub.removeItem(this.filters, filter);
  }

  public setAccountInfo(uin: number, userName: string): void  {
    console.info('MarsServiceStub-------setAccountInfo '+userName )
    this.accountInfo.uin = uin;
    this.accountInfo.userName = userName;
  }

  public setForeground(isForeground: number): void  {
    //    BaseEvent.onForeground(isForeground == 1);
  }

  public makesureAuthed(host: string): boolean  {
    // Allow you to block all tasks which need to be sent before certain 'AUTHENTICATED' actions
    // Usually we use this to exchange encryption keys, sessions, etc.
    //
    return true;
  }

  public onNewDns(host: string): string[]  {
    var defaultDns = ["123.57.87.139"];
    return defaultDns;
  }

  public onPush(cmdid: number, taskid: number, data: ArrayBuffer): void {
    console.info("onPush cmdid:"+cmdid+", taskid:"+taskid+", data:"+this.Uint8ArrayToString(new Uint8Array(data)));
    for (let filter of this.filters) {
      try {
        if (filter.onRecv(cmdid, data)) {
          break;
        }
      } catch (err) {
        console.error(`fail callback, code: ${err.code}, msg: ${err.msg}`)
      }
    }
    MainService.getInstance().process(new PushMessage(cmdid, data));
  }

  public Uint8ArrayToString(array: Uint8Array ): string {
    let out: string = '';
    let i: number = 0;
    let len: number = 0;
    let c: number = -1;
    let char2: number = -1;
    let char3: number = -1;
    let tempAry: Uint8Array = array;
    len = tempAry.length;
    i = 0;
    while (i < len) {
      c = tempAry[i++];
      switch (c >> 4) {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        // 0xxxxxxx
          out += String.fromCharCode(c);
          break;
        case 12:
        case 13:
        // 110x xxxx   10xx xxxx
          char2 = tempAry[i++];
          out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
          break;
        case 14:
        // 1110 xxxx  10xx xxxx  10xx xxxx
          char2 = tempAry[i++];
          char3 = tempAry[i++];
          out += String.fromCharCode(((c & 0x0F) << 12) |
            ((char2 & 0x3F) << 6) |
            ((char3 & 0x3F) << 0));
          break;
      }
    }
    return out;
  }

  private text2buffer(text: string): ArrayBuffer {
    //TODO: NEED IMPROVED.
    let buffer = new ArrayBuffer(text.length);
    let view = new Uint8Array(buffer);
    for(let i=0; i<text.length; i++) {
      view[i] = text.charCodeAt(i);
    }
    return buffer;
  }

  public static Uint8Array2String(fileData) {
    var dataString = "";
    for (var i = 0; i < fileData.length; i++) {
      dataString += String.fromCharCode(fileData[i]);
    }
    return dataString
  }
  public trafficData(send: number, recv: number): void {
    console.info("processing push message, send: " + send + ", recv:" + recv);
    let buffer = this.text2buffer(send + "," + recv);
//    const buffer = new TextEncoder().encode(send + "," + recv);
    this.onPush(BaseConstants.FLOW_CMDID, 0, buffer);
  }

  public reportConnectInfo(status: number, longlinkstatus: number): void {
    console.info("reportConnectInfo status: " + status + ", longlinkstatus:" + longlinkstatus);
  }

  public getLongLinkIdentifyCheckBuffer(identifyReqBuf: any, hashCodeBuffer: any, reqRespCmdID: number[]): number{
    // Send identify request buf to server
    // identifyReqBuf.write();
    console.info("getLongLinkIdentifyCheckBuffer");
    return StnLogic.ECHECK_NEVER;
  }

  public onLongLinkIdentifyResp(buffer: any[], hashCodeBuffer: any[]): boolean {
    console.info("onLongLinkIdentifyResp");
    return false;
  }

  public requestDoSync(): void {
    console.info("requestDoSync");
  }

  public requestNetCheckShortLinkHosts(): string[]{
    console.info("requestNetCheckShortLinkHosts");
    return new String[0];
  }

  public isLogoned(): boolean {
    return false;
  }

  public onTaskEnd(taskID: number, userContext: any, errType: number, errCode: number, profile: any): number {
    hilog.info(0x0000, "[__mars-ohos__]", 'executes the onTaskEnd function callback value to js, taskID ==%{public}d', taskID);
    let wrapper = MarsServiceStub.TASK_ID_TO_WRAPPER.get(taskID);
    MarsServiceStub.TASK_ID_TO_WRAPPER.delete(taskID);
    if (wrapper == null) {
      console.info("stn task onTaskEnd callback may fail, null wrapper, taskID=%d", taskID);
      return 0;
    }
    try {
      wrapper.onTaskEnd(errType, errCode);
    } catch (err) {
      console.error(`fail callback, code: ${err.code}, msg: ${err.msg}`)
    }

    return 0;
  }

  public req2Buf(taskID: number, userContext: Object, errCode: number[], channelSelect: number, host: string): ArrayBuffer {
    console.info("enter MarsServiceStub.req2Buf taskID:"+taskID+", userContext:"+userContext+", errCode:"+errCode+", channelSelect:"+channelSelect+", host:"+host);
    const wrapper = MarsServiceStub.TASK_ID_TO_WRAPPER.get(taskID);
    console.info("wrapper:"+wrapper);
    if (wrapper == null) {
      console.info("invalid req2Buf for task, taskID=" + taskID);
      return null;
    }

    try {
      console.info("req2Buf calling wrapper.req2buf");
      return wrapper.req2buf();

    } catch (err) {
      console.error(`task wrapper req2buf failed for short, check your encode process code: ${err.code}, msg: ${err.msg}`)
    }

    return null;
  }

  public buf2Resp(taskID: number, userContext: Object, respBuffer: ArrayBuffer, errCode: number[], channelSelect: number): number {
    const wrapper = MarsServiceStub.TASK_ID_TO_WRAPPER.get(taskID);
    if (wrapper == null) {
      console.info("buf2Resp: wrapper not found for stn task, taskID=" + taskID);
      return StnLogic.RESP_FAIL_HANDLE_TASK_END;
    }

    try {
      console.info("buf2Resp calling wrapper.buf2resp respBuffer");
      return wrapper.buf2resp(respBuffer);

    } catch (err) {
      console.error(`remote wrapper disconnected, clean this context code: ${err.code}, msg: ${err.msg}`)


      MarsServiceStub.TASK_ID_TO_WRAPPER.delete(taskID);
    }
    return StnLogic.RESP_FAIL_HANDLE_TASK_END;
  }

  public reportTaskProfile(reportString: string): void{
    console.log("reportTaskProfile ");
    let buffer = this.text2buffer(reportString);
    this.onPush(BaseConstants.CGIHISTORY_CMDID, 0, buffer);
  }

  public reportSignalDetectResults(reportString: string): void {
    let buffer = this.text2buffer(reportString);
    this.onPush(BaseConstants.SDTRESULT_CMDID, 0, buffer);
  }

  //AppLogic_ICallBack
  public getAppFilePath(): string {
    console.log("zjj: MarsServiceStub getAppFilePath is null ");
//      try {
//          File file = context.getFilesDir();
//          if (!file.exists()) {
//              file.createNewFile();
//          }
//          return file.toString();
//      } catch (E e) {
//          Log.e(TAG, "", e);
//      }
    return null;
  }

  public getAccountInfo(): AppLogic.AccountInfo {
    return this.accountInfo;
  }

  public getClientVersion(): number {
    return this.clientVersion;
  }

  public getDeviceType(): AppLogic.DeviceInfo {
    return this.info;
  }
}

